package cyan.gf.controller;

import cyan.gf.control.ControlUtils;
import cyan.gf.control.MessageBox;
import cyan.gf.data.ControllerDataInitializable;
import cyan.gf.data.Table;
import cyan.gf.db.CrudInstance;
import cyan.gf.db.JdbcProp;
import cyan.gf.db.Schema;
import cyan.gf.db.TableField;
import cyan.gf.generator.*;
import cyan.gf.sql.MySQLDao;
import cyan.gf.sql.OracleDao;
import cyan.gf.util.NameUtil;
import cyan.gf.util.PropReader;
import cyan.gf.util.StagePool;
import freemarker.template.TemplateException;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.*;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.time.LocalDate;
import java.util.*;
import java.util.List;

/**
 * Created by cyan on 2017/1/23.
 * Email:qsyan@vip.qq.com
 */
public class GenerateController implements Initializable, ControllerDataInitializable {

  protected static Logger logger = LogManager.getLogger(GenerateController.class);

  @FXML
  Button choicePathButton;
  @FXML
  TableView tableView;
  @FXML
  TableColumn<Table, CheckBox> selectCol;
  @FXML
  TableColumn<Table, String> tableNameCol, mappingNameCol;
  @FXML
  TextField outPutPathField, entityPackageField, daoPackageField, artifactIdField, groupIdField,
      servicePackageField, controllerPackageField, authorField, basePackageField;
  @FXML
  DatePicker datePicker;
  @FXML
  CheckBox entitySupportCheckBox, daoSupportCheckBox, serviceSupportCheckBox,
      mvcSupportCheckBox, jpaSupportCheckBox, configSupportCheckBox, mavenSupportCheckBox;
  @FXML
  ComboBox<String> springVersionComboBox, hibernateVersionComboBox;
  @FXML
  ProgressIndicator progressIndicator;
  private List<String> tableNames;
  private String currentSchemaName;
  CrudInstance crudInstance;
  ObservableList<Table> data;
  ContextMenu contextMenu;
  private static String CURRENT_STAGE_ID = "generator";
  Schema schema;

  @Override
  public void initialize(URL location, ResourceBundle resources) {
    LocalDate localDate = LocalDate.now();
    ObservableList<String> springVersions = getArrayFromPropAsObservableList("spring.versions");
    ObservableList<String> hibernateVersions = getArrayFromPropAsObservableList("hibernate.versions");
    progressIndicator.setVisible(false);
    mappingNameCol.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().getMappingName()));
    tableNameCol.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().getTableName()));
    selectCol.setCellValueFactory(param -> param.getValue().getItemCheckBox());
    datePicker.setValue(localDate);
    springVersionComboBox.setItems(springVersions);
    hibernateVersionComboBox.setItems(hibernateVersions);
    springVersionComboBox.getSelectionModel().select(0);
    hibernateVersionComboBox.getSelectionModel().select(0);
    contextMenu = new ContextMenu();
    MenuItem selectAllMenuItem = new MenuItem("select All");
    MenuItem unSelectAllMenuItem = new MenuItem("unselect All");
    contextMenu.getItems().add(selectAllMenuItem);
    contextMenu.getItems().add(unSelectAllMenuItem);
    selectAllMenuItem.setOnAction(event -> {
      ObservableList<Table> items = tableView.getItems();
      items.forEach(table -> {
        CheckBox checkBox = table.getItemCheckBox().getValue();
        checkBox.setSelected(true);
      });
    });
    unSelectAllMenuItem.setOnAction(event -> {
      ObservableList<Table> items = tableView.getItems();
      items.forEach(table -> {
        CheckBox checkBox = table.getItemCheckBox().getValue();
        checkBox.setSelected(false);
      });
    });
    tableView.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
      if (event.getButton() == MouseButton.SECONDARY) {
        contextMenu.show(tableView, event.getScreenX(), event.getScreenY());
      }
    });
    daoSupportCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
          daoPackageField.setDisable(!newValue);
          if (newValue) {
            entitySupportCheckBox.setSelected(newValue);
          } else {
            serviceSupportCheckBox.setSelected(false);
          }
        }
    );
    serviceSupportCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
          servicePackageField.setDisable(!newValue);
          if (newValue) {
            daoSupportCheckBox.setSelected(true);
          } else {
            mvcSupportCheckBox.setSelected(false);
          }
        }
    );
    entitySupportCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
      entityPackageField.setDisable(!newValue);
      if (!newValue) {
        daoSupportCheckBox.setSelected(false);
        serviceSupportCheckBox.setSelected(false);
        mvcSupportCheckBox.setSelected(false);
      }
    });
    mvcSupportCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
      controllerPackageField.setDisable(!newValue);
      if (newValue) {
        serviceSupportCheckBox.setSelected(true);
      }
    });
    mavenSupportCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
      springVersionComboBox.setDisable(!newValue);
      hibernateVersionComboBox.setDisable(!newValue);
      groupIdField.setDisable(!newValue);
      artifactIdField.setDisable(!newValue);
    });
    basePackageField.textProperty().addListener((observable, oldValue, newValue) -> {
      newValue = newValue.toLowerCase().trim();
      if (!StringUtils.isBlank(newValue)) {
        entityPackageField.setText(newValue + ".entity");
        servicePackageField.setText(newValue + ".service");
        daoPackageField.setText(newValue + ".dao");
        controllerPackageField.setText(newValue + ".controller");
        groupIdField.setText(newValue);
      } else {
        entityPackageField.clear();
        servicePackageField.clear();
        daoPackageField.clear();
        controllerPackageField.clear();
        groupIdField.clear();
      }
    });
  }

  public ObservableList getArrayFromPropAsObservableList(String propKey) {
    ObservableList<String> stringObservableList = FXCollections.observableArrayList();
    PropReader.load("/config/config.properties");
    String[] items = PropReader.getString(propKey).split(",");
    for (String item : items) {
      stringObservableList.add(item);
    }
    return stringObservableList;
  }

  public void openChoice() {
    Stage currentStage = StagePool.get(CURRENT_STAGE_ID);
    DirectoryChooser directoryChooser = new DirectoryChooser();
    File file = directoryChooser.showDialog(currentStage);
    if (file != null)
      outPutPathField.setText(file.getAbsolutePath());
  }

  public void runGenerate() {
    if (ControlUtils.isInputBlank(outPutPathField, basePackageField)) {
      progressIndicator.setVisible(true);
      ObservableList<Table> tables = ControlUtils.getSelectedItems(tableView);
      if (tables.size() == 0) {
        MessageBox.warn(CURRENT_STAGE_ID, "No table seleced.");
        return;
      }
      new Thread(() -> {
        if (configSupportCheckBox.isSelected()) {
          processConfig();
          logger.debug("process config...");
        }
        if (mavenSupportCheckBox.isSelected()) {
          processPom();
          logger.debug("process pom...");
        }
        tables.forEach(table -> {
          if (entitySupportCheckBox.isSelected()) {
            processEntity(table);
            logger.debug("process entity...");
          }
          if (daoSupportCheckBox.isSelected()) {
            processDao(table);
            logger.debug("process dao...");
          }
          if (serviceSupportCheckBox.isSelected()) {
            processService(table);
            logger.debug("process service...");
          }
          if (mvcSupportCheckBox.isSelected()) {
            processController(table);
            logger.debug("process controller...");
          }
        });
        Platform.runLater(() -> {
              progressIndicator.setVisible(false);
              Optional result = MessageBox.info(CURRENT_STAGE_ID, "Operation has completed successfully.");
              if (result.get() == ButtonType.OK) {
                try {
                  Desktop.getDesktop().open(new File(outPutPathField.getText().trim()));
                } catch (IOException e) {
                  logger.error(e);
                }
              }
            }
        );
      }).start();
    } else {
      MessageBox.warn(CURRENT_STAGE_ID, "The basePackage and output path must be filled.");
    }
  }

  public void processEntity(Table table) {
    String outDir = getSourceDir() + mkPackageDirs(entityPackageField.getText().trim());
    File file = new File(outDir);
    if (!file.exists()) {
      file.mkdirs();
    }
    String tableName = table.getTableName();
    List<TableField> tableFields = new ArrayList<>();
    if (schema == Schema.MYSQL) {
      MySQLDao mySQLDao = new MySQLDao(crudInstance);
      tableFields = mySQLDao.getTableFields(currentSchemaName, tableName);
    }
    if (schema == Schema.ORACLE) {
      OracleDao oracleDao = new OracleDao(crudInstance);
      tableFields = oracleDao.getTableFields(tableName);
    }
    Map data = new HashMap();
    String destFile = outDir + table.getMappingName() + ".java";
    data.put("entityPackage", entityPackageField.getText());
    data.put("author", authorField.getText());
    data.put("date", datePicker.getValue());
    data.put("className", table.getMappingName());
    data.put("tableName", tableName);
    data.put("schemaName", currentSchemaName);
    data.put("jpaSupport", jpaSupportCheckBox.isSelected());
    data.put("fields", tableFields);
    try {
      EntityGenerator entityGenerator = new EntityGenerator(destFile);
      entityGenerator.setData(data);
      entityGenerator.process();
    } catch (IOException | TemplateException e) {
      logger.error(e);
    }
  }

  public void processDao(Table table) {
    String outDir = getSourceDir() + mkPackageDirs(daoPackageField.getText().trim());
    File file = new File(outDir);
    if (!file.exists()) {
      file.mkdirs();
    }
    Map data = new HashMap();
    data.put("className", table.getMappingName());
    data.put("daoPackage", daoPackageField.getText().trim());
    data.put("entityPackage", entityPackageField.getText().trim());
    data.put("author", authorField.getText());
    data.put("date", datePicker.getValue());
    try {
      DaoGenerator daoGenerator = new DaoGenerator(outDir + table.getMappingName() + "Dao.java");
      DaoImplGenerator daoImplGenerator = new DaoImplGenerator(outDir + table.getMappingName() + "DaoImpl.java");
      daoGenerator.setData(data);
      daoImplGenerator.setData(data);
      daoGenerator.process();
      daoImplGenerator.process();
    } catch (IOException | TemplateException e) {
      logger.error(e);
    }
  }

  public void processService(Table table) {
    String outDir = getSourceDir() + mkPackageDirs(servicePackageField.getText().trim());
    File file = new File(outDir);
    if (!file.exists()) {
      file.mkdirs();
    }
    Map data = new HashMap();
    data.put("className", table.getMappingName());
    data.put("servicePackage", servicePackageField.getText().trim());
    data.put("daoPackage", daoPackageField.getText().trim());
    data.put("entityPackage", entityPackageField.getText().trim());
    data.put("classFieldName", NameUtil.asFieldName(table.getMappingName()));
    data.put("author", authorField.getText());
    data.put("date", datePicker.getValue());
    try {
      ServiceGenerator serviceGenerator = new ServiceGenerator(outDir + table.getMappingName() + "Service.java");
      ServiceImplGenerator serviceImplGenerator = new ServiceImplGenerator(outDir + table.getMappingName() + "ServiceImpl.java");
      serviceGenerator.setData(data);
      serviceImplGenerator.setData(data);
      serviceGenerator.process();
      serviceImplGenerator.process();
    } catch (IOException | TemplateException e) {
      logger.error(e);
    }
  }

  public void processController(Table table) {
    String outDir = getSourceDir() + mkPackageDirs(controllerPackageField.getText().trim());
    File file = new File(outDir);
    if (!file.exists()) {
      file.mkdirs();
    }
    Map data = new HashMap();
    data.put("className", table.getMappingName());
    data.put("controllerPackage", controllerPackageField.getText().trim());
    data.put("servicePackage", servicePackageField.getText().trim());
    data.put("entityPackage", entityPackageField.getText().trim());
    data.put("classFieldName", NameUtil.asFieldName(table.getTableName()));
    data.put("author", authorField.getText());
    data.put("date", datePicker.getValue());
    try {
      ControllerGenerator controllerGenerator = new ControllerGenerator(outDir + table.getMappingName() + "Controller.java");
      controllerGenerator.setData(data);
      controllerGenerator.process();
    } catch (IOException | TemplateException e) {
      logger.error(e);
    }
  }

  public void processConfig() {
    String webConfigOutDir = getWebappDir();
    String propertiesOutDir = getResourcesDir();
    File configfile = new File(webConfigOutDir);
    File propertiesfile = new File(propertiesOutDir);
    if (!configfile.exists()) {
      configfile.mkdirs();
    }
    if (!propertiesfile.exists()) {
      propertiesfile.mkdirs();
    }
    Map data = new HashMap();
    JdbcProp jdbcProp = crudInstance.getJdbcProp();
    if (schema == Schema.MYSQL) {
      jdbcProp.setUrl(jdbcProp.getUrl() + currentSchemaName);
    }
    data.put("jdbc", jdbcProp);
    data.put("schema", schema.text);
    data.put("schemaName", currentSchemaName);
    data.put("basePackage", basePackageField.getText().trim());
    try {
      ApplicationContextGenerator applicationContextGenerator =
          new ApplicationContextGenerator(webConfigOutDir + "applicationContext.xml");
      DispatcherServletGenerator dispatcherServletGenerator = new DispatcherServletGenerator(webConfigOutDir + "dispatcher-servlet.xml");
      DbPropertiesGenerator mysqlPropertiesGenerator = new DbPropertiesGenerator(propertiesOutDir + "db.properties");
      WebxmlGenerator webxmlGenerator = new WebxmlGenerator(webConfigOutDir + "web.xml");
      applicationContextGenerator.setData(data);
      dispatcherServletGenerator.setData(data);
      mysqlPropertiesGenerator.setData(data);
      applicationContextGenerator.process();
      dispatcherServletGenerator.process();
      mysqlPropertiesGenerator.process();
      webxmlGenerator.process();
    } catch (IOException | TemplateException e) {
      logger.error(e);
    }
  }

  public void processPom() {
    String outDir = outPutPathField.getText() + "\\";
    String groupId = groupIdField.getText().trim();
    String artifactId = artifactIdField.getText().trim();
    Map data = new HashMap();
    data.put("groupId", groupId);
    data.put("schema", schema.text);
    data.put("artifactId", artifactId);
    data.put("springVersion", springVersionComboBox.getValue());
    data.put("hibernateVersion", hibernateVersionComboBox.getValue());
    try {
      PomGenerator pomGenerator = new PomGenerator(outDir + "pom.xml");
      pomGenerator.setData(data);
      pomGenerator.process();
    } catch (IOException | TemplateException e) {
      logger.error(e);
    }
  }


  public String mkPackageDirs(String packageName) {
    if (StringUtils.isBlank(packageName)) {
      return "";
    }
    String[] dirs = packageName.split("\\.");
    StringBuilder stringBuilder = new StringBuilder();
    for (String dir : dirs) {
      stringBuilder.append(dir + "\\");
    }
    return stringBuilder.toString();
  }

  public String getSourceDir() {
    return outPutPathField.getText() + "\\src\\main\\java\\";
  }

  public String getResourcesDir() {
    return outPutPathField.getText() + "\\src\\main\\resources\\";
  }

  public String getWebappDir() {
    return outPutPathField.getText() + "\\src\\main\\webapp\\WEB-INF\\";
  }

  @Override
  public void initData(Map<String, Object> dataMap) {
    data = FXCollections.observableArrayList();
    this.currentSchemaName = dataMap.get("currentSchemaName").toString();
    this.tableNames = (List<String>) dataMap.get("tableNames");
    this.crudInstance = (CrudInstance) dataMap.get("crudInstance");
    this.schema = (Schema) dataMap.get("schema");
    choicePathButton.requestFocus();
    for (String name : tableNames) {
      data.add(new Table(name, NameUtil.asClassName(name)));
    }
    tableView.setItems(data);
  }
}
